type mode =
  | Performant   (* N'affiche rien, réfléchit 5 secondes *)
  | SuiviPoints  (* Affiche le suivi des points par tour *)
  | SuiviReflexion (* Donne toutes les informations sur la réflexion de l'IA *)

let tictoc () =
  let temps_total = ref 0. in
  let temps_debut = ref 0. in
  let tic () =
    temps_debut := Unix.gettimeofday ()
  in
  let toc () =
    temps_total := !temps_total +.
		     ((Unix.gettimeofday ())
		      -. !temps_debut)
  in
  let resultat () = !temps_total in
  (tic, toc, resultat)

let adapter_chaine ch =
  let longueur =
    let i = ref 0 in
    let () =
      while !i < String.length ch && ch.[!i] <> char_of_int 0 do
	incr i
      done
    in
    !i
  in
  String.sub ch 0 longueur

let jouer erreur_acceptable nombre_infini temps_limite_ecart temps_limite_prise temps_limite_jeu suivi_partie mode_encheres mode_ecart mode_jeu toujours_prendre temps_reflexion nom_a_prendre invitation input output =
  let debug = false in
  let client = Tarotv.make () in
  let lecteur = Protocole.lire_message () in
  let open Protocole in
  let open Tarotv in
  let proba_faire = ref None in
  let nombre_cycles = ref 0 in
  let nombre_choix = ref 0 in
  let points = ref 0. in
  let bouts = ref 0. in
  let points_empoches = ref 0. in
  let temps_total = ref 0. in
  let temps_distribution = ref 0. in
  let temps_resolution = ref 0. in
  let ligne () =
    let () = if mode_jeu = SuiviPoints then
	       let (dp, b) = Tarotv.points client (Tarotv.joueur_appele client) in
	       Printf.printf
		 "%d %f %f %f %f %d %d %d %f %f %f\n%!"
		 (Tarotv.nombre_plis_joues client)
		 !points
		 !bouts
		 !points_empoches
		 ((float_of_int dp) /. 2.)
		 b
		 !nombre_cycles
		 !nombre_choix
		 !temps_total
		 !temps_distribution
		 !temps_resolution
    in
    ()
  in
  let assimiler = function
    | Deconnexion ->
       let () =
	 if debug then
	   Printf.printf "Assimilation de déconnexion.\n%!" in
       assimiler_information_deconnexion client
    | ErreurProtocole | Refuse ->
			 let () =
			   if debug then
			     print_endline "Refuse" in
			 let () = assimiler_information_erreur client in
			 if doit_priser client then
			   failwith "Erreur de prise"
			 else if doit_ecarter client then
			   failwith "Erreur d'écart"
			 else if doit_jouer client then
			   failwith "Erreur de jeu"
			 else ()
    | Identifier n ->
       let () =
	 if debug then
	   Printf.printf "Identifier %s\n%!" n in
       assimiler_requete_identifier client n
    | Inviter joueurs ->
       let () =
	 if debug then
	   Printf.printf "Inviter [|%s; %s; %s; %s|]\n%!"
       			 (joueurs.(0)) (joueurs.(1))
       			 (joueurs.(2)) (joueurs.(3)) in
       assimiler_requete_inviter client (Array.to_list joueurs)
    | Entree nom ->
       let () =
	 if debug then
	   Printf.printf "Entree %s\n%!" nom in
       assimiler_information_entree client nom
    | Sortie nom ->
       let () =
	 if debug then
	   Printf.printf "Sortie %s\n%!" nom in
       assimiler_information_sortie client nom
    | Noms tab ->
       let () =
	 if debug then
	   Printf.printf "Noms [|%s; %s; %s; %s; %s|]\n%!"
       			      (tab.(0)) (tab.(1)) (tab.(2))
       			      (tab.(3)) (tab.(4)) in
       assimiler_information_noms client tab
    | Numero k ->
       let () =
	 if debug then
	   Printf.printf "Numero %d\n%!" k in
       assimiler_information_numero client k
    | Distribution cartes ->
       let () =
	 if debug then
	   Printf.printf
       	     "Distribution [|%s|]\n%!"
       	     (String.concat
       		"; "
       		(List.map (Carte.string_of_carte)
       			  (Array.to_list cartes))) in
       assimiler_information_distribution client 
       (Array.to_list cartes)
    | Prise k ->
       let () =
	 if debug then
	   Printf.printf "Prise %d\n%!" k in
       assimiler_requete_prise client k
    | Contrat k ->
       let () =
	 if debug then
	   Printf.printf "Contrat %d\n%!" k in
       assimiler_information_contrat client k
    | Appel ->
       let () =
	 if debug then
	   Printf.printf "Appel\n%!" in
       assimiler_information_appel client
    | Appeler carte ->
       let () =
	 if debug then
	   Printf.printf "Appeler %s\n%!"
       			      (Carte.string_of_carte carte) in
       assimiler_requete_appel client carte
    | ContratFinal (preneur, prise, appelee) ->
       let () =
	 if debug then
	   Printf.printf
       		  "ContratFinal (%d, %d, %s)\n%!"
       		  preneur prise (Carte.string_of_carte appelee) in
       assimiler_information_contrat_final client preneur prise appelee 
    | Chien tab ->
       let () =
	 if debug then
	   Printf.printf "Chien [|%s; %s; %s|]\n%!"
       			      (Carte.string_of_carte tab.(0))
       			      (Carte.string_of_carte tab.(1))
       			      (Carte.string_of_carte tab.(2)) in
       assimiler_information_chien client (Array.to_list tab)
    | Ecart tab ->
       let () =
	 if debug then
	   print_endline "Ecart" in
       assimiler_requete_ecart client (Array.to_list tab)
    | Atout liste ->
       let () =
	 if debug then
	   print_endline "Atout" in
       assimiler_information_atout_au_chien client liste
    | Jeu ->
       let () =
	 if debug then
	   print_endline "Jeu" in
       let () = assimiler_information_jeu client in
       if mode_jeu = SuiviPoints then
	 Printf.printf
	   "nom \"%s\"\n\"Numéro de tour\" \"Points espérés\" \"Bouts escomptés\" \"Points empochés espérés\" \"Points accumulés\" \"Bouts accumulés\" \"Nombre de cycles fructueux\" \"Nombre de choix possibles\" \"Temps passé à réfléchir\" \"Temps passé à distribuer\" \"Temps passé à résoudre\"\n%!"
	   nom_a_prendre
    | Requete carte ->
       let () =
	 if debug then
	   print_endline "Requete" in
       assimiler_requete_carte client carte
    | Carte carte ->
       let () =
	 if debug then
	   print_endline "Carte" in
       assimiler_information_carte client carte
    | Pli joueur ->
       let () =
	 if debug then
	   print_endline "Pli" in
       let () = assimiler_information_pli client joueur in
       ligne ()
    | Resultat tab ->
       let () =
	 if debug then
	   print_endline "Resultat" in
       let mon_numero =
	 match Tarotv.mon_numero client with
	 | None -> failwith "Ne peut arriver"
	 | Some x -> x
       in
       let preneur = match Tarotv.preneur client with
	 | None -> failwith "Ne peut arriver"
	 | Some i -> i
       in
       let appele = match Tarotv.joueur_appele client with
	 | None -> preneur
	 | Some x -> x
       in
       let () =
	 if suivi_partie then
	   let noms = Tarotv.mes_adversaires client in
	   Array.iteri
	     (fun joueur res ->
	      Printf.printf "\"%s\" %s %d\n%!"
			    (adapter_chaine noms.(joueur))
			    (if joueur = preneur || joueur = appele
			     then "attaque" else "défense")
			    res)
	     tab
       in
       let () =
	 if toujours_prendre then
	   let () =
	     match !proba_faire with
	     | Some p when preneur = mon_numero -> 
		let () = Printf.printf "\"Probabilité initiale de la faire :\" %f %s\n%!"
				       p
				       (if tab.(mon_numero) > 0
					then "OK"
					else "ÉCHEC")
		in
		()
	     | _ -> ()
	   in
	   ()
       in
       let () =
	 let cartes_preneur =
	   Array.init
	     15
	     (fun tour ->
	      List.map
	   	(fun (_, carte, _) -> carte)
	   	(List.filter
	   	   (fun (i, carte, _) ->
	   	    i = preneur && carte <> Carte.fausse_excuse)
	   	   (Tarotv.repartition_cartes client tour)))
	 in
	 let (_, enchere) = Tarotv.enchere_maximale client in
	 let cartes_preneur = Array.map (List.hd) cartes_preneur in
	 let cartes_preneur = Array.map (Carte.int_of_carte) cartes_preneur in
	 let rec inserer i =
	   if i >= 1 then
	     if cartes_preneur.(i) < cartes_preneur.(i - 1) then
	       let tmp = cartes_preneur.(i) in
	       let () = cartes_preneur.(i) <-
	   		  cartes_preneur.(i - 1) in
	       let () = cartes_preneur.(i - 1) <- tmp in
	       inserer (i - 1)
	     else ()
	   else ()
	 in
	 let () =
	   for i = 1 to -1 + Array.length cartes_preneur do
	     inserer i
	   done
	 in
	 let cartes_preneur = Array.to_list cartes_preneur in
	 let cartes_preneur = List.map (Carte.carte_of_int) cartes_preneur in
	 let str_cartes_preneur = List.map (Carte.string_of_carte) cartes_preneur in
	 let vraisemblable = Decision.vraisemblable cartes_preneur enchere in
	 let () = Printf.printf "Jeu du preneur : %s\n%!"
				(String.concat ", " str_cartes_preneur) in
	 Printf.printf "Vraisemblable : %f\n%!" vraisemblable
       in
       assimiler_information_resultat client tab
  in
  let deconnecter () = close_in input ; close_out output in
  let () = Tarotv.assimiler_information_connexion client in
  let envoyer_message msg =
    if msg = Protocole.Deconnexion then deconnecter ()
    else
      let octets = Protocole.ecrire_message msg in
      let () = assimiler msg in
      let () = Array.iter (output_byte output) octets in
      flush output
  in
  let chaines_egales str1 str2 =
    let rec aux i =
      if i < String.length str1 && i < String.length str2 then
	if str1.[i] = str2.[i] then aux (i + 1)
	else false
      else if i = String.length str1 && i = String.length str2 then true
      else if i = String.length str1 then
	str2.[i] = char_of_int 0
      else if i = String.length str2 then
	str1.[i] = char_of_int 0
      else false

    in
    aux 0
  in
  let verifier_invitation () =
    match invitation with
    | None -> ()
    | Some liste ->
       let presents = Tarotv.joueurs_disponibles client in
       let ok = List.map
		  (fun necessaire ->
		   List.exists (chaines_egales necessaire) presents)
		  liste in
       let tous_ok = List.fold_left (&&) true ok in
       if tous_ok && not (Tarotv.requete_en_cours client)
       then
	 envoyer_message (Inviter (Array.of_list liste))
       else
	 ()
  in
  let rec attendre_message () =
    try
      let octet = input_byte input in
      match lecteur octet with
      | None -> attendre_message ()
      | Some msg -> 
	 let () = assimiler msg in
	 let () =
	   match msg with
	   | Protocole.Entree _ ->
	      verifier_invitation ()
	   | _ -> ()
	 in
	 msg
    with
    | exc ->
       let () = Printf.printf "Connexion rompue (%s).\n%!"
			      (Printexc.to_string exc) in
       let msg = Protocole.Deconnexion in
       let () = try deconnecter () with _ -> () in
       let () = assimiler msg in
       msg
  in
  let attendre_identification () =
    let () =
      if est_connecte client then
	match attendre_message () with
	| Refuse
	| ErreurProtocole -> failwith "Nom_pris"
	| _ -> ()
      else ()
    in
    ()
  in
  let attendre_partie () =
    while est_connecte client
	  && mon_nom client <> ""
	  && not (en_jeu client) do
      let () = ignore (attendre_message ()) in
      verifier_invitation ()
    done
  in
  let appel_calcule = ref None in
  let jouer () =
    let travailler () =
      let open Carte in
      let _ = attendre_message () in
      let () =
	if Tarotv.requete_en_cours client then
	  let () =
	    if debug then
	      print_endline "-> Rien à faire, requête en cours."
	  in
	  ()
	else if doit_priser client then
	  let () =
	    if debug then
	      print_endline "-> je dois priser."
	  in
	  let () = nombre_cycles := 0 in
	  let (tic1, toc1, res1) = tictoc () in
	  let (tic2, toc2, res2) = tictoc () in
	  let (tictot, toctot, restot) = tictoc () in
	  let () = tictot () in
	  let () = nombre_choix := List.length (Tarotv.cartes_appelables client) in
	  let (travailler, resultat) =
	    Decision.prendre
	      ~afficher:(mode_encheres = SuiviReflexion)
	      ~tictoc1:(tic1, toc1)
	      ~tictoc2:(tic2, toc2)
	      client in
	  let suivireflexion_continuer = ref true in
	  let proba_ok = ref 0. in
	  let nombre_fois_ok = ref 0 in
	  let fin_reflexion = temps_limite_prise +. Unix.gettimeofday () in
	  let () =
	    while
	      (mode_encheres = SuiviReflexion && !suivireflexion_continuer)
	      || (!nombre_fois_ok < nombre_infini * !nombre_choix
		  && Unix.gettimeofday () < fin_reflexion)
	    do
	      let () = travailler () in
	      let () =
		if mode_encheres = SuiviReflexion then
		  let () =
		    Printf.printf "Entrez du texte pour arrêter là, ou appuyez sur entrée pour continuer.\n%!"
		  in
		  if read_line () <> "" then
		    suivireflexion_continuer := false
	      in
	      let () =
		let (_, ok, _, _) = resultat () in
		if abs_float (!proba_ok -. ok) < erreur_acceptable then
		  incr nombre_fois_ok
		else
		  let () = proba_ok := ok in
		  nombre_fois_ok := 1
	      in
	      incr nombre_cycles
	    done
	  in
	  let (appelee, ok, points_esperes, bouts_esperes) = resultat () in
	  let () = appel_calcule := Some appelee in
	  let () = points := points_esperes in
	  let () = bouts := bouts_esperes in
	  let () = toctot () in
	  let (_, derniere_enchere) = enchere_maximale client in
	  let (res1, res2, restot) = (res1 (), res2 (), restot ()) in
	  let () =
	    if mode_encheres = SuiviPoints then
	      Printf.printf
		"\"Probabilité de la faire :\" %f\n\"Meilleure carte à appeler :\" \"%s\"\n\"Points espérés (avec optimisme) :\" %f\n\"Bouts escomptés :\" %f\n\"Nombre de cycles fructueux\" \"Nombre de choix possibles\" \"Temps total\" \"Temps passé à distribuer\" \"Temps passé à résoudre\"\n%d %d %f %f %f\n%!"
		ok (Carte.string_of_carte appelee) points_esperes bouts_esperes !nombre_cycles !nombre_choix restot res1 res2
	  in
	  let () = proba_faire := Some ok in
	  let () =
	    match derniere_enchere with
	    | -1 | 0 | 1 when toujours_prendre && doit_priser client ->
	       envoyer_message (Prise 2)
	    | -1 | 0 | 1 | 2 when ok > 0.7 ->
			envoyer_message (Prise 3)
	    | -1 | 0 | 1 when ok > 0.5 ->
			envoyer_message (Prise 2)
	    | 0 when ok > 0.35 ->
	       envoyer_message (Prise 1)
	    | _ -> envoyer_message (Prise 0)
	  in
	  ()
	else if doit_appeler client then
	  let () =
	    if debug then
	      print_endline "-> je dois faire un appel."
	  in
	  match !appel_calcule with
	  | None -> failwith "Wait... je dois faire un appel, mais je n'ai pas calculé mon enchère ??? !!!"
	  | Some carte -> envoyer_message (Appeler carte)
	else if doit_ecarter client then
	  let () =
	    if debug then
	      print_endline "-> je dois faire un écart."
	  in
	  match !appel_calcule with
	  | None ->
	     let () = Printf.printf "J'ai mangé une étape.\n%!" in
	     failwith "pas d'appel calculé"
	  | Some appel ->
	     let () = nombre_cycles := 0 in
	     let (tic1, toc1, res1) = tictoc () in
	     let (tic2, toc2, res2) = tictoc () in
	     let (tictot, toctot, restot) = tictoc () in
	     let () = tictot () in
	     let (travailler, resultat, n_choix) =
	       Decision.ecarter
		 ~afficher:(mode_ecart = SuiviReflexion)
		 ~tictoc1:(tic1, toc1)
		 ~tictoc2:(tic2, toc2)
		 client in
	     let () = nombre_choix := n_choix in
	     let () = nombre_cycles := 0 in
	     let ecart_choisi = ref [] in
	     let simplifier_ecart e =
	       List.map
		 (fun (v, c) -> if v <= 10 && c <> Carte.Atout then (1, c)
				else (v, c))
		 e (* Réduit le nombre d'écarts différents *)
	     in
	     let nombre_fois_choisi = ref 0 in
	     let () =
	       let suivireflexion_continuer = ref true in
	       let fin_reflexion = temps_limite_ecart +. Unix.gettimeofday () in
	       while (mode_ecart = SuiviReflexion && !suivireflexion_continuer)
		     || (mode_ecart <> SuiviReflexion
			 && !nombre_fois_choisi < nombre_infini * n_choix
			 && Unix.gettimeofday () < fin_reflexion) do
		 let () = travailler () in
		 let () =
		   if mode_ecart = SuiviReflexion then
		     let () =
		       Printf.printf "Entrez du texte pour arrêter là, ou appuyez sur entrée pour continuer.\n%!"
		     in
		     if read_line () <> "" then
		       suivireflexion_continuer := false
		 in
		 let () =
		   let (e2, _) = resultat () in
		   if (simplifier_ecart e2) = (simplifier_ecart !ecart_choisi)
		   then incr nombre_fois_choisi
		   else let () = nombre_fois_choisi := 0 in
			ecart_choisi := e2
		 in
		 incr nombre_cycles
	       done
	     in
	     let (ecart, points_esperes) = resultat () in
	     let () = points := points_esperes in
	     let () = toctot () in
	     let (res1, res2, restot) = (res1 (), res2 (), restot ()) in
	     let () =
	       if mode_ecart = SuiviPoints then
		 let (a, b, c) =
		   match ecart with
		   | [a ; b ; c] ->
		      (Carte.string_of_carte a, Carte.string_of_carte b, Carte.string_of_carte c)
		   | _ -> failwith "Impossible."
		 in
		 Printf.printf
		   "\"Meilleur écart :\" \"%s\" \"%s\" \"%s\"\n\"Points empochés espérés :\" %f\n\"Nombre de cycles fructueux\" \"Nombre de choix possibles\" \"Temps total\" \"Temps passé à distribuer\" \"Temps passé à résoudre\"\n%d %d %f %f %f\n%!"
		   (a:string)
		   (b:string)
		   (c:string)
		   (points_esperes:float)
		   (!nombre_cycles:int)
		   (!nombre_choix:int)
		   (restot:float)
		   (res1:float)
		   (res2:float)
	     in
	     let () = envoyer_message (Ecart (Array.of_list ecart)) in
	     ()
	else if doit_jouer client then
	  let () =
	    if debug then
	      print_endline "-> je dois jouer."
	  in
	  let () = nombre_choix := List.length (Tarotv.cartes_jouables client) in
	  let (tic1, toc1, res1) = tictoc () in
	  let (tic2, toc2, res2) = tictoc () in
	  let (tictot, toctot, restot) = tictoc () in
	  let () = tictot () in
	  let (travailler, resultat) =
	    try
	      Decision.jouer
		~afficher:(mode_jeu = SuiviReflexion)
		~tictoc1:(tic1, toc1)
		~tictoc2:(tic2, toc2)
		client
	    with e -> failwith ("Exception dans client_unix.ml, cas doit_jouer : "^(Printexc.to_string e))
	  in
	  let () = nombre_cycles := 0 in
	  let () =
	    let suivireflexion_continuer = ref true in
	    let meilleur_choix = ref None in
	    let nombre_fois_choisi = ref 0 in
	    let fin_reflexion = temps_limite_jeu +. Unix.gettimeofday () in
	    while (mode_jeu = SuiviReflexion && !suivireflexion_continuer)
		  || (mode_jeu <> SuiviReflexion
		      && !nombre_fois_choisi < nombre_infini * !nombre_choix
		      && Unix.gettimeofday () < fin_reflexion) do
	      let () = travailler () in
	      let () =
		if mode_jeu = SuiviReflexion then
		  let () =
		    Printf.printf "Entrez du texte pour arrêter là, ou appuyez sur entrée pour continuer.\n%!"
		  in
		  if read_line () <> "" then
		    suivireflexion_continuer := false
	      in
	      let () =
		let (c2, _, _, _, _) = resultat () in
		if !meilleur_choix = Some c2 then
		  incr nombre_fois_choisi
		else
		  let () = nombre_fois_choisi := 0 in
		  meilleur_choix := Some c2
	      in
	      ()
	    done
	  in
	  let (carte_jouee, points_esperes, bouts_esperes, pe_empoches, n_reussis) =
	    resultat () in
	  let () = toctot () in
	  let () = temps_total := restot () in
	  let () = temps_distribution := res1 () in
	  let () = temps_resolution := res2 () in
	  let () = nombre_cycles := n_reussis in
	  let () = points := points_esperes in
	  let () = points_empoches := pe_empoches in
	  let () = bouts := bouts_esperes in
	  let () = envoyer_message (Protocole.Requete carte_jouee) in
	  ()
	else
	  let () =
	    if debug then
	      print_endline "-> rien à faire."
	  in
	  ()
      in
      ()
    in
    let () = 
      while est_connecte client do
	travailler ()
      done
    in
    ()
  in
  (* On s'identifie *)
  let () = envoyer_message (Protocole.Identifier nom_a_prendre) in
  let () = attendre_identification () in
  let () = attendre_partie () in
  let () = jouer () in
  ()
